﻿using System;
using System.IO;

namespace Aeon.Emulator.Mouse
{
    /// <summary>
    /// Assists the mouse handler in tracking mouse click events.
    /// </summary>
    internal sealed class ButtonPressTracker
    {
        #region Constructors
        /// <summary>
        /// Initializes a new instance of the ButtonPressTracker class.
        /// </summary>
        public ButtonPressTracker()
        {
        }
        #endregion

        #region Public Methods
        /// <summary>
        /// Notifies the tracker of a button press.
        /// </summary>
        /// <param name="button">Pressed button.</param>
        /// <param name="x">X-coordinate of the cursor.</param>
        /// <param name="y">Y-courdinate of the cursor.</param>
        public void ButtonPress(MouseButtons button, int x, int y)
        {
            switch(button)
            {
            case MouseButtons.Left:
                UpdateButtonInfo(ref leftPress, x, y);
                break;

            case MouseButtons.Right:
                UpdateButtonInfo(ref rightPress, x, y);
                break;

            case MouseButtons.Middle:
                UpdateButtonInfo(ref middlePress, x, y);
                break;

            default:
                throw new ArgumentOutOfRangeException("button");
            }
        }
        /// <summary>
        /// Notifies the tracker of a button release.
        /// </summary>
        /// <param name="button">Released button.</param>
        /// <param name="x">X-coordinate of the cursor.</param>
        /// <param name="y">Y-courdinate of the cursor.</param>
        public void ButtonRelease(MouseButtons button, int x, int y)
        {
            switch(button)
            {
            case MouseButtons.Left:
                UpdateButtonInfo(ref leftRelease, x, y);
                break;

            case MouseButtons.Right:
                UpdateButtonInfo(ref rightRelease, x, y);
                break;

            case MouseButtons.Middle:
                UpdateButtonInfo(ref middleRelease, x, y);
                break;

            default:
                throw new ArgumentOutOfRangeException("button");
            }
        }
        /// <summary>
        /// Returns information about a pressed button.
        /// </summary>
        /// <param name="button">Index of button to get information for.</param>
        /// <returns>Information about the button press.</returns>
        public ButtonInfo GetButtonPressInfo(int button)
        {
            ButtonInfo result;

            switch(button)
            {
            case 0:
                result = leftPress;
                leftPress.Count = 0;
                break;

            case 1:
                result = rightPress;
                rightPress.Count = 0;
                break;

            case 2:
                result = middlePress;
                middlePress.Count = 0;
                break;

            default:
                throw new ArgumentOutOfRangeException("button");
            }

            return result;
        }
        /// <summary>
        /// Returns information about a released button.
        /// </summary>
        /// <param name="button">Index of button to get information for.</param>
        /// <returns>Information about the button release.</returns>
        public ButtonInfo GetButtonReleaseInfo(int button)
        {
            ButtonInfo result;

            switch(button)
            {
            case 0:
                result = leftRelease;
                leftRelease.Count = 0;
                break;

            case 1:
                result = rightRelease;
                rightRelease.Count = 0;
                break;

            case 2:
                result = middleRelease;
                middleRelease.Count = 0;
                break;

            default:
                throw new ArgumentOutOfRangeException("button");
            }

            return result;
        }
        /// <summary>
        /// Invoked when the device should save its current state to a <see cref="System.IO.BinaryWriter"/>.
        /// </summary>
        /// <param name="writer"><see cref="System.IO.BinaryWriter"/> to which the state is saved.</param>
        public void SaveCurrentState(BinaryWriter writer)
        {
            Action<ButtonInfo> writeButtonInfo = b =>
                {
                    writer.Write(b.Count);
                    writer.Write(b.X);
                    writer.Write(b.Y);
                };

            writeButtonInfo(this.leftPress);
            writeButtonInfo(this.rightPress);
            writeButtonInfo(this.middlePress);
            writeButtonInfo(this.leftRelease);
            writeButtonInfo(this.rightRelease);
            writeButtonInfo(this.middleRelease);
        }
        /// <summary>
        /// Invoked when the device should restore its state from a <see cref="System.IO.BinaryReader"/>.
        /// </summary>
        /// <param name="reader"><see cref="System.IO.BinaryReader"/> from which the state is restored.</param>
        public void RestoreState(BinaryReader reader)
        {
            Func<ButtonInfo> readButtonInfo = () =>
                new ButtonInfo
                {
                    Count = reader.ReadUInt32(),
                    X = reader.ReadInt32(),
                    Y = reader.ReadInt32()
                };

            this.leftPress = readButtonInfo();
            this.rightPress = readButtonInfo();
            this.middlePress = readButtonInfo();
            this.leftRelease = readButtonInfo();
            this.rightRelease = readButtonInfo();
            this.middleRelease = readButtonInfo();
        }
        #endregion

        #region Private Static Methods
        /// <summary>
        /// Increments the counter and assigns a new position to a ButtonInfo struct.
        /// </summary>
        /// <param name="info">Reference to struct to update.</param>
        /// <param name="x">New X-coordinate of the cursor.</param>
        /// <param name="y">New Y-courdinate of the cursor.</param>
        private static void UpdateButtonInfo(ref ButtonInfo info, int x, int y)
        {
            info.Count++;
            info.X = x;
            info.Y = y;
        }
        #endregion

        #region Private Fields
        private ButtonInfo leftPress;
        private ButtonInfo rightPress;
        private ButtonInfo middlePress;
        private ButtonInfo leftRelease;
        private ButtonInfo rightRelease;
        private ButtonInfo middleRelease;
        #endregion
    }

    /// <summary>
    /// Stores information about a button press or release.
    /// </summary>
    internal struct ButtonInfo
    {
        /// <summary>
        /// Number of times the button was pressed or released.
        /// </summary>
        public uint Count;
        /// <summary>
        /// X-coordinate of the cursor at the most recent event.
        /// </summary>
        public int X;
        /// <summary>
        /// Y-coordinate of the cursor at the most recent event.
        /// </summary>
        public int Y;
    }
}
